package com.nage.components.movement;

import com.nage.components.display.Shape;
import com.nage.engine.GameObject;
import com.nage.graphics.RenderSystem;

/**
 * RandomWaypointMovement will move a single object towards any number of
 * defined waypoints with a certain amount of randomness. 
 * 
 * Each waypoint has a weight which determines how likely the object 
 * is to head towards it, as well as the proximity of the object to
 * the waypoint. 
 * 
 */
public class RandomWaypointMovement extends Shape {
	
	/** 
	 * Reference to the objects physics component. Needed as
	 *	random waypoint movement changes the speed etc. of the
	 *	game object by changing physics component variables.
	 */
	//private Physics physics;
	
	/**
	 * Two arrays which contain the x and y coordinates of any waypoints,
	 * and the weight of each waypoint (0 - 1).
	 * Default size is 5. Should expand to hold more if needed.
	 */
	private float[] waypointX, waypointY, waypointWeight;
	
	/**
	 * Index ito waypointX and waypointY showing which waypoint the
	 * object is currently targetting, and the number of waypoints defined.
	 */
	private int currentTarget, numberOfWaypoints;

	/**
	 * Define the distance in each direction that a game object will
	 * consider that it has arrived at the waypoint
	 */
	private float xDistanceLeft, xDistanceRight, yDistanceUp, yDistanceDown;
	
	/**
	 * Milliseconds since changeTarget() was called.
	 */
	private int timeSinceTargetChange;

	/**
	 * Time at which changeTarget() should be called.
	 */
	private double nextTargetChange = 1000;

	
	public RandomWaypointMovement(GameObject p) {
		super(p);
		type = RANDOM_WAYPOINT_MOVEMENT;
		waypointX = new float[5];
		waypointY = new float[5];
		waypointWeight = new float[5];
		
		addWaypoint(100, 100, 0.8f);
		addWaypoint(400, 400, 0.5f);
		addWaypoint(100, 400, 0.8f);
		addWaypoint(400, 100, 0.5f);
		addWaypoint(RenderSystem.view[2], RenderSystem.view[3], 0.2f);

		pickRandomWaypoint();
		
	}

	/**
	 * Checks if game object is within bounds on the target waypoint
	 * 
	 * Each call to update will compare the position of the parent game object
	 * and see if it is within the bounds of the target waypoint, defined by
	 * xDistanceLeft/Right and yDistanceDown/Up. 
	 * 
	 * @param delta 	The time since update was last called
	 * @param input		An Input object that contains all user input since last update
	 * @return			this returned so that a square is drawn at the point of the target waypoint.
	 * 
	 */
	@Override
	public void update(long delta) {
			
		// Is the object within the boundaries of the waypoint?
		if(parent.x > xDistanceLeft && parent.x < xDistanceRight 
				&& parent.y > yDistanceDown && parent.y < yDistanceUp) {
			//Log.d("Waypoint", "Object reached waypoint. New one will be picked.");
			pickRandomWaypoint();
		}
		
		if(timeSinceTargetChange > nextTargetChange) {
			changeTarget(0.5);
			timeSinceTargetChange = 0;
		} else {
			timeSinceTargetChange += delta;
		}
				
	}
	
	/**
	 * Changes the position that the Physics component will aim towards
	 * 
	 * This will change the value of the variables within the physics component
	 * to be slightly randomised depending on the variablity factor. A higher
	 * variability means that the new target could be within a wide range of 
	 * distances from the actual waypoint, while low variability means it will
	 * stick to quite a close range. The target is not changed to something that 
	 * is really close to the current position as this causes strange behaviour.
	 * If the target < 100px to the current position, then the actual waypoint 
	 * is used as the target.
	 * 
	 * @param variability	Controls maximum distance that target can be from waypoint
	 * @throws IllegalArgumentException	variability must be between 0 and 1.
	 * 
	 */
	private void changeTarget(double variability) throws IllegalArgumentException {
		
		if(variability < 0 || variability > 1) {
			throw new IllegalArgumentException("variability must be between 0 and 1.");
		}
		
		float spread = (float) ((200 - Math.random() * 400)*variability);
		float xRnd = waypointX[currentTarget] + spread;
		float yRnd = waypointY[currentTarget] + spread;
		
		// When object gets within 100px, it aims directly for target
		if(Math.abs(parent.x - xRnd) < 100 && Math.abs(parent.y - yRnd) < 100) {
			xRnd = waypointX[currentTarget];
			yRnd = waypointY[currentTarget];
		}
		
		//Log.d("Waypoint", "Target randomised: X = "+waypointX[currentTarget]+" Xrnd = "+xRnd
			//	+ "Y = "+waypointY[currentTarget]+" Yrnd = "+yRnd);
		//physics.setTarget(xRnd, yRnd);
	}

	/**
	 * Sets the current waypoint to be the next one in the array. Cycles back to index 0.
	 */
	@SuppressWarnings("unused")
	private void pickNextWaypoint() {
		currentTarget++;
		if(currentTarget==numberOfWaypoints) {
			currentTarget = 0;
		}
		x = waypointX[currentTarget];
		y = waypointY[currentTarget];
		setWaypointBoundary();
	//	physics.setTarget(waypointX[currentTarget], waypointY[currentTarget]); 
	}
	
	/**
	 * Sets the current waypoint to be a random one from the array, based on the weight of each.
	 */
	private void pickRandomWaypoint() {
		// find the sum of all weights
		float sum = 0;
		for (float w : waypointWeight) {
			sum += w;
		}
		
		// random number between 0 and sum.
		double rnd = Math.random() * sum; 
		
		for(int i = 0; i<numberOfWaypoints; i++) {
			if(rnd < waypointWeight[i]) {
				if(currentTarget==i) {
					return;
				}
				currentTarget = i;
				x = waypointX[currentTarget];
				y = waypointY[currentTarget];
				setWaypointBoundary();
		//		physics.setTarget(waypointX[currentTarget], waypointY[currentTarget]); 
				// tell the physics component 
				// to move towards the waypoint
				// using whatever physics settings
				// are set up on the object	
				return;
			}
			rnd -= waypointWeight[i];
		}
	}
	
	/**
	 * 	 Set the distance from waypoint at which an object will consider that it has arrived
	 *	 and therefore start looking for a new waypoint. The stronger the weight of a waypoint,
	 *	 the closer is attracts objects. eg. weight = 1 (strongest) means it must be withing 20px
	 *	 in any direction. weight = 0 means it must be withing 100px in an direction.
	 */
	private void setWaypointBoundary() {

		xDistanceLeft = waypointX[currentTarget] - (50 - (40*waypointWeight[currentTarget]));
		xDistanceRight = waypointX[currentTarget] + (50 - (40*waypointWeight[currentTarget]));
		yDistanceUp = waypointY[currentTarget] + (50 - (40*waypointWeight[currentTarget]));
		yDistanceDown = waypointY[currentTarget] - (50 - (40*waypointWeight[currentTarget]));
	}
	
	/**
	 * Insert a new waypoint for the game object to possibly move towards
	 * @param x		X coordinate of waypoint
	 * @param y		Y coordinate of waypoint
	 * @param weight Weight of waypoint
	 */
	private void addWaypoint(float x, float y, float weight) {
		waypointX[numberOfWaypoints] = x;
		waypointY[numberOfWaypoints] = y;
		waypointWeight[numberOfWaypoints] = weight;
		numberOfWaypoints++; // now contains index of next waypoint that will be added
		if(numberOfWaypoints >= waypointX.length) {
			// increase the size of waypoint arrays if they have run out of space
			float[] xTemp = new float[numberOfWaypoints + 5];
			float[] yTemp = new float[numberOfWaypoints + 5];
			float[] weightTemp = new float[numberOfWaypoints + 5];
			System.arraycopy(waypointX, 0, xTemp, 0, numberOfWaypoints);
			System.arraycopy(waypointY, 0, yTemp, 0, numberOfWaypoints);
			System.arraycopy(waypointWeight, 0, weightTemp, 0, numberOfWaypoints);
			waypointX = xTemp;
			waypointY = yTemp;
			waypointWeight = weightTemp;
		}
		
	}



}
